package errors

import (
	"fmt"
)

type CodeError interface {
	error
	Code() int
	Is(target error) bool
}

type withCode struct {
	code  int
	msg   string
	cause error
	*stack
}

func (w *withCode) Error() string {
	return fmt.Sprintf("%v", w)
}

func (w *withCode) Is(target error) bool {
	if errCode, ok := target.(*withCode); ok && w.code == errCode.code {
		return true
	}
	return false
}

func (w *withCode) Code() int {
	return w.code
}

func WithCode(err error, code int, message string) error {
	return &withCode{
		code:  code,
		msg:   message,
		cause: err,
		stack: callers(),
	}
}

func WithCodef(err error, code int, format string, args ...interface{}) error {
	return WithCode(err, code, fmt.Sprintf(format, args...))
}

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
//	type causer interface {
//	       Cause() error
//	}
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
	type causer interface {
		Cause() error
	}

	for err != nil {
		cause, ok := err.(causer)
		if !ok {
			break
		}

		if cause.Cause() == nil {
			break
		}

		err = cause.Cause()
	}
	return err
}

func WithMessage(err error, s string) error {
	return WithCodef(err, 0, s)
}

func New(message string) error {
	return WithCode(nil, 0, message)
}

func ErrorWithCause(err CodeError, cause error) CodeError {
	return &withCode{
		code:  err.Code(),
		msg:   err.Error(),
		cause: cause,
		stack: callers(),
	}
}

func NewCodeError(code int, message string) CodeError {
	return &withCode{
		code: code,
		msg:  message,
	}
}
